#pragma once

#define KEYDOWN(name, key) ((name[key] & 0x80) ? true : false)
#define KEYUP(name, key) ((name[key] & 0x80) ? false : true)
#define MOUSEBUTTONDOWN(name, button) ((name.rgbButtons[button] & 0x80) ? true : false)
#define MOUSEBUTTONUP(name, button) ((name.rgbButtons[button] & 0x80) ? false : true)

class Input
{
// classes/structs
private:
	typedef struct _Controller
	{
		XINPUT_STATE Xstate;
		XINPUT_STATE prevXstate;
		float vibratingTime;
		bool connected;
		bool vibrating;
		_Controller() { connected = false; vibratingTime = 0.0f; vibrating = false; }
	} Controller;

// variables
private:
	//**********************************************************************//
	//	DirectInput Variables												//
	//**********************************************************************//
	IDirectInput8* din;														//
	IDirectInputDevice8* kb;												//
	IDirectInputDevice8* mouse;												//
	//**********************************************************************//

	//**********************************************************************//
	//	States																//
	//**********************************************************************//
	DIMOUSESTATE mouseState;												//
	DIMOUSESTATE prevMouseState;											//
	D3DXVECTOR2	cursorPos;													//
	D3DXVECTOR2 cursorChange;												//
	char keys[256];															//
	char prevKeys[256];														//
	Controller controllers[4];												//
	//**********************************************************************//

// functions
private:
	//**********************************************************************//
	//	Singleton															//
	//**********************************************************************//
	Input(void);
	Input(Input&);
	~Input(void);
	Input& operator=(Input&);
	
public:
	static Input* GetInstance() { static Input instance; return &instance; }
	//**********************************************************************//

	//**********************************************************************//
	//	Function Name: Init													//
	//	Arguments: void														//
	//	Return Type: void													//
	//	Return Notes: 														//
	//	Scope: public														//
	//	Last Modified: 5/26/2009 12:37:13 PM								//
	//	Purpose: Create all necessary DirectInput and XINPUT objects		//
	//**********************************************************************//
	void Init();

	//**********************************************************************//
	//	Function Name: Shutdown												//
	//	Arguments: void														//
	//	Return Type: void													//
	//	Return Notes: 														//
	//	Scope: public														//
	//	Last Modified: 5/26/2009 12:38:21 PM								//
	//	Purpose: Releases all DirectInput objects							//
	//**********************************************************************//
	void Shutdown();

	//**********************************************************************//
	//	Function Name: Update												//
	//	Arguments:															//
	//	Return Type: void													//
	//	Return Notes: 														//
	//	Scope: public														//
	//	Last Modified: 5/26/2009 12:39:09 PM								//
	//	Purpose: Update and DirectInput and XINPUT objects					//
	//**********************************************************************//
	void Update();

	//**********************************************************************//
	//	Key/Gamepad/Mouse Functions											//
	//**********************************************************************//
	FORCEINLINE D3DXVECTOR2 GamepadLeftThumbstick(const DWORD _index = 0) const { return D3DXVECTOR2(controllers[_index].Xstate.Gamepad.sThumbLX / 32767.0f, controllers[_index].Xstate.Gamepad.sThumbLY / 32767.0f); }
	FORCEINLINE float GamepadLeftThumbstickX(const DWORD _index = 0) const { return controllers[_index].Xstate.Gamepad.sThumbLX / 32767.0f; }
	FORCEINLINE float GamepadLeftThumbstickY(const DWORD _index = 0) const { return controllers[_index].Xstate.Gamepad.sThumbLY / 32767.0f; }
	FORCEINLINE D3DXVECTOR2 GamepadRightThumbstick(const DWORD _index = 0) const { return D3DXVECTOR2(controllers[_index].Xstate.Gamepad.sThumbRX / 32767.0f, controllers[_index].Xstate.Gamepad.sThumbRY / 32767.0f); }
	FORCEINLINE float GamepadRightThumbstickX(const DWORD _index = 0) const { return controllers[_index].Xstate.Gamepad.sThumbRX / 32767.0f; } // TODO optimize this by just storing this when the update is done, just add vars to struct
	FORCEINLINE float GamepadRightThumbstickY(const DWORD _index = 0) const { return controllers[_index].Xstate.Gamepad.sThumbRY / 32767.0f; }
	FORCEINLINE float GamepadRightTrigger(const DWORD _index = 0) const { return controllers[_index].Xstate.Gamepad.bRightTrigger / 255.0f; }
	FORCEINLINE float GamepadLeftTrigger(const DWORD _index = 0) const { return controllers[_index].Xstate.Gamepad.bRightTrigger / 255.0f; }
	FORCEINLINE bool IsGamepadButtonUp(const WORD _button, const DWORD _index = 0) { return (controllers[_index].Xstate.Gamepad.wButtons & _button) ? false : true; }
	FORCEINLINE bool IsGamepadButtonDown(const WORD _button, const DWORD _index = 0) { return (controllers[_index].Xstate.Gamepad.wButtons & _button) ? true : false; }
	FORCEINLINE bool IsGamepadButtonPressed(const WORD _button, const DWORD _index = 0) { return (!(controllers[_index].Xstate.Gamepad.wButtons & _button) && (controllers[_index].prevXstate.Gamepad.wButtons & _button)); }
	FORCEINLINE bool IsGamepadConnected(const DWORD _index = 0) const { return controllers[_index].connected; }
	FORCEINLINE bool IsAGamepadButtonDown(const DWORD _index = 0) const { return (controllers[_index].Xstate.Gamepad.wButtons) ? true : false; }
	bool IsAGamepadButtonPressed(const DWORD _index = 0);
	WORD GetGamepadButtonDown(const DWORD _index = 0);
	WORD GetGamepadButtonPressed(const DWORD _index = 0);
	FORCEINLINE void ResetGamepad(const DWORD _index = 0) { ZeroMemory(&controllers[_index].Xstate.Gamepad, sizeof(XINPUT_GAMEPAD)); ZeroMemory(&controllers[_index].prevXstate.Gamepad, sizeof(XINPUT_GAMEPAD)); }
	FORCEINLINE void ResetGamepadButtons(const DWORD _index = 0) { controllers[_index].Xstate.Gamepad.wButtons = 0; controllers[_index].prevXstate.Gamepad.wButtons = 0; }
	void ResetGamepadThumbsticks(const DWORD _index = 0);
	void ResetGamepadTriggers(const DWORD _index = 0);
	void VibrateGamepad(const float _time, const float _leftSpeed, const float _rightSpeed, const DWORD _index = 0);
	void StopVibratingGamepad(const DWORD _index = 0);
	FORCEINLINE bool IsGamepadVibrating(const DWORD _index = 0) const { return controllers[_index].vibrating; }
	FORCEINLINE bool IsKeyUp(const byte _key) const { return KEYUP(keys, _key); }
	FORCEINLINE bool IsKeyDown(const byte _key) const { return KEYDOWN(keys, _key); }
	FORCEINLINE bool IsKeyPressed(const byte _key) const { return (KEYDOWN(prevKeys, _key) && KEYUP(keys, _key)); }
	bool IsAKeyDown() const;
	bool IsAKeyPressed() const;
	char GetKeyDownASCII() const;
	char GetKeyPressedASCII() const;
	byte GetKeyDown() const;
	byte GetKeyPressed() const;
	INLINE void ResetKeys() { ZeroMemory(keys, 256); ZeroMemory(prevKeys, 256); }

	FORCEINLINE const D3DXVECTOR2& GetMousePosition() const { return cursorPos; }
	FORCEINLINE const D3DXVECTOR2& GetMousePositionChange() const { return cursorChange; }
	FORCEINLINE long GetMouseWheelDelta() const { return mouseState.lZ; }
	FORCEINLINE bool IsMouseButtonDown(const byte _button) const { return MOUSEBUTTONDOWN(mouseState, _button); }
	FORCEINLINE bool IsMouseButtonPressed(const byte _button) const { return MOUSEBUTTONDOWN(prevMouseState, _button) && MOUSEBUTTONUP(mouseState, _button); }
	//**********************************************************************//
	
	//**********************************************************************//
	//	IDirectInputDevice8* kb Accessor									//
	//**********************************************************************//
	FORCEINLINE IDirectInputDevice8* GetKeyboardDevice() const { return kb; }
	//**********************************************************************//

	//**********************************************************************//
	//	IDirectInputDevice8* mouse Accessor									//
	//**********************************************************************//
	FORCEINLINE IDirectInputDevice8* GetMouseDevice() const { return mouse; }
	//**********************************************************************//

	FORCEINLINE IDirectInputDevice8* GetDevice(GUID _guid); // TODO
};

DECLARE_SINGLETON_H(Input);
